package domain;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Pattern;

import javax.swing.ImageIcon;

import pagemaker.ExternalProgramException;
import pagemaker.MuPDFPageMaker;

public class PDFdoc implements Comparable<PDFdoc> {

    private static int counter;
    private static final int DPI = 20;
    private int id;
    private String name;
    private String docPath;
    private String title;
    private ArrayList<String> tags;
    private ArrayList<String> authors;
    private int nbPages;
    private int curPage;
    private MuPDFPageMaker doc;

    /**
     * Create a instance of PDFdoc.
     *
     * @param file path to a file.
     * @param nbPages number of pages to be proceed.
     * @throws ExternalProgramException.
     * @throws IOException.
     */
    private PDFdoc(String file, int nbPages) throws IOException, ExternalProgramException {
        this(new File(file), nbPages);
    }

    /**
     * Create a instance of PDFdoc.
     *
     * @param file a file
     * @param nbPages number of pages to be proceed
     * @throws IOException.
     * @throws ExternalProgramException.
     */
    private PDFdoc(File file, int nbPages) throws IOException, ExternalProgramException {
        this();
        name = file.getName();
        docPath = file.getPath();
        doc = new MuPDFPageMaker(file, nbPages);
        this.nbPages = nbPages;
    }

    /**
     * Create a instance of PDFdoc.
     *
     * @param file a file.
     * @throws IOException.
     * @throws ExternalProgramException.
     */
    private PDFdoc(File file) throws IOException, ExternalProgramException {
        this();
        name = file.getName();
        docPath = file.getPath();
        doc = new MuPDFPageMaker(file);
        this.nbPages = doc.getNumberOfPages();
    }

    /**
     * Create a instance of PDFdoc.
     */
    private PDFdoc() {
        super();
        title = null;
        tags = new ArrayList<>();
        authors = new ArrayList<>();
        curPage = 1;
        id = ++counter;
    }

    /**
     * Returns an instance of PDFdoc.
     *
     * @param f a File.
     * @return PDFdoc.
     * @requires Parameter f is a PDF file that exists on the file system.
     * @throws IOException.
     * @throws ExternalProgramException.
     */
    public static PDFdoc makePDFdoc(File f) throws IOException, ExternalProgramException {
        return new PDFdoc(f);
    }

    /**
     * Returns the set of tags as a string, separated by the given separator.
     *
     * @param separator separator of tags.
     * @return the set of tags as a string.
     */
    public String tagsToString(String separator) {
        StringBuilder str = new StringBuilder();
        int i = 1;
        for (String s : tags) {
            if (i < tags.size()) {
                str.append(s);
                str.append(separator);
                i++;
            } else {
                str.append(s);
            }
        }
        return str.toString();
    }

    /**
     * Return the of the document.
     *
     * @return the tags of the document.
     */
    public ArrayList<String> getTags() {
        ArrayList<String> result = new ArrayList<>();
        for (String tag : tags) {
            result.add(tag.toUpperCase());
        }
        return result;
    }

    /**
     * Sets the title of the document.
     *
     * @param title new title for this document.
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * Get the title of the document.
     *
     * @return the title of the document.
     */
    public String getTitle() {
        if (this.title == null) {
            return name;
        } else {
            return title;
        }
    }

    /**
     * Get the Id number of the document.
     *
     * @return The number of the document. (unique ID).
     */
    public int getDocumentNumber() {
        return id;
    }

    /**
     * Given a string that contains tags separated by the given separator, adds
     * the tags to the document.
     *
     * @param tagsString string that contains tags separated by the given
     * separator.
     * @param separator separator of tags in given string.
     */
    public void addTags(String tagsString, String separator) {
        tags.clear();
        tags.addAll(Arrays.asList(tagsString.split(Pattern.quote(separator))));
    }

    /**
     * Given a string that contains authors names separated by the given
     * separator, adds the authors to the document.
     *
     * @param authors string that contains authors names separated by the given
     * separator.
     * @param separator separator of authors in given string.
     */
    public void setAuthors(String authors, String separator) {
        this.authors.clear();
        this.authors.addAll(Arrays.asList(authors.split(Pattern.quote(separator))));
    }

    /**
     * Returns the set of authors as a string, separated by the given separator.
     *
     * @param separator separator of authors.
     * @return the set of authors as a string.
     */
    public String authorsToString(String separator) {
        StringBuilder str = new StringBuilder();
        int i = 1;
        for (String s : authors) {
            if (i < authors.size()) {
                str.append(s);
                str.append(separator);
                i++;
            } else {
                str.append(s);
            }
        }
        return str.toString();
    }

    /**
     * Returns an ImageIcon that represent the current page.
     *
     * @return ImageIcon that represent the current page.
     * @throws IOException.
     * @throws ExternalProgramException.
     */
    public ImageIcon getCurrentPage() throws IOException, ExternalProgramException {
        return doc.getPage(curPage, DPI);
    }

    /**
     * Returns an ImageIcon that represent the next page.
     *
     * @return ImageIcon that represent the next page.
     * @throws IOException.
     * @throws ExternalProgramException.
     * @requires getNbPages() > 0.
     */
    public ImageIcon getNextPage() throws IOException, ExternalProgramException {
        if (curPage + 1 <= getNumberOfPages()) {
            return doc.getPage(++curPage, DPI);
        } else {
            return doc.getPage(curPage, DPI);
        }
    }

    /**
     * Returns an ImageIcon that represent the previous page.
     *
     * @return ImageIcon that represent the previous page.
     * @throws IOException.
     * @throws ExternalProgramException.
     */
    public ImageIcon getPreviousPage() throws IOException, ExternalProgramException {
        if (curPage - 1 >= 1) {
            return doc.getPage(--curPage, DPI);
        } else {
            return doc.getPage(curPage, DPI);
        }
    }

    /**
     * Returns a string representation of the object.
     *
     * @return string representation of the object.
     */
    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        str.append(getDocumentNumber());
        str.append(". ");
        str.append(getTitle());
        str.append(" [");
        str.append(tagsToString(",").toUpperCase());
        str.append("]");
        return str.toString();
    }

    /**
     * Compares this object with the specified object for order. Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.
     *
     * @param o the object to be compared.
     * @return a negative integer, zero, or a positive integer as this object is
     * less than, equal to, or greater than the specified object.
     */
    @Override
    public int compareTo(PDFdoc o) {
        return getTitle().compareTo(o.getTitle());
    }

    /**
     * Returns a hash code value for the object.
     *
     * @return hash code value for this object.
     */
    @Override
    public int hashCode() {
        int hash = 17;
        hash = 31 * hash + Objects.hashCode(this.docPath);
        return hash;
    }

    /**
     * Verifies if 2 documents are the same. It compares the file path of both
     * of them.
     *
     * @param o Other Object to compare with this.
     * @return true if Object o is equals to this Object.
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof PDFdoc)) {
            return false;
        }
        PDFdoc d = (PDFdoc) o;
        return d.getFilePath().equals(this.getFilePath());
    }

    /**
     * Returns the number of pages of the document.
     *
     * @return number of pages of the document.
     */
    public int getNumberOfPages() {
        return nbPages;
    }

    /**
     * Returns the number of the current page.
     *
     * @return number of the current page.
     */
    public int getCurrentPageNumber() {
        return curPage;
    }

    /**
     * Given a current page it changes the value of the actual page.
     *
     * @param curPage new current page.
     */
    public void setCurPage(int curPage) {
        this.curPage = curPage;
    }

    /**
     * Returns the file path of the document.
     *
     * @return file path of the document.
     */
    public String getFilePath() {
        return docPath;
    }
}
